﻿using System;
using System.Collections.Generic;
//using System.Linq;
using System.Text;
using System.IO;

namespace thesebas.php.serialization
{
    public class UnserializeException : Exception
    {
        public UnserializeException(string msg) : base(msg) { }

    }
    public class PHPUnserializer
    {
        StringReader reader;
        protected PHPUnserializer(string str)
        {

            this.reader = new StringReader(Encoding.ASCII.GetString(Encoding.UTF8.GetBytes(str)));


        }

        protected int readInt()
        {
            StringBuilder sb = new StringBuilder();
            bool stop = false;
            do
            {
                int i = this.reader.Peek();

                if (i > 0)
                {
                    char c = Convert.ToChar(i);
                    if (char.IsDigit(c) || c == '+' || c == '-')
                    {
                        this.reader.Read();
                        sb.Append(c);
                    }
                    else
                    {
                        return int.Parse(sb.ToString());
                    }
                }
                else
                {
                    return int.Parse(sb.ToString());
                }
            } while (!stop);
            return int.Parse(sb.ToString());

        }
        private double readFloat()
        {
            StringBuilder sb = new StringBuilder();
            bool stop = false;
            do
            {
                int i = this.reader.Peek();

                if (i > 0)
                {
                    char c = Convert.ToChar(i);
                    if (char.IsNumber(c) || c == '+' || c == '-' || c == '.' || char.ToLower(c) == 'e')
                    {
                        this.reader.Read();
                        sb.Append(c);
                    }
                    else
                    {
                        return double.Parse(sb.ToString(), PHPFloat.Format);
                    }
                }
                else
                {
                    return double.Parse(sb.ToString());
                }
            } while (!stop);
            return double.Parse(sb.ToString());
        }
        protected string readString(int length)
        {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < length; i++)
                sb.Append(Convert.ToChar(this.reader.Read()));

            return sb.ToString();

        }
        protected bool readString(string str)
        {
            char[] chrs = str.ToCharArray();
            foreach (char c in chrs)
            {
                if (this.peekAndReadChar(c)) return false;
            }
            return true;
        }

        protected void readChar(char ch)
        {
            int di = this.reader.Read();
            int ci = Convert.ToInt32(ch);
            if (ci != di) throw new UnserializeException(String.Format("reading char: [{0}] expected, got [{1}]", ch, Convert.ToChar(di)));
        }
        private bool peekAndReadChar(char ch)
        {
            int di = this.reader.Peek();
            int ci = Convert.ToInt32(ch);
            if (ci == di) { this.reader.Read(); return true; }
            return false;
        }

        protected PHPVariable UnserializeVariable()
        {

            switch (Convert.ToChar(this.reader.Peek()))
            {
                case 's':
                    return this.UnserializeString();
                case 'N':
                    return this.UnserializeNull();
                case 'i':
                    return this.UnserializeInt();
                case 'd':
                    return this.UnserializeFloat();
                case 'b':
                    return this.UnserializeBoolean();
                case 'a':
                    return this.UnserializeArray();
                case 'O':
                    return this.UnserializeObject();




            }
            return null;
        }

        private PHPBoolean UnserializeBoolean()
        {
            this.readChar('b');
            this.readChar(':');
            PHPBoolean b;
            if (this.peekAndReadChar('1'))
            {
                b = PHPBoolean.True;
            }
            else if (this.peekAndReadChar('0'))
            {
                b = PHPBoolean.False;
            }
            else
            {
                throw new UnserializeException(String.Format("error unserializing boolean, (0|1) expected, {1} found", Convert.ToChar(this.reader.Peek())));
            }
            this.readChar(';');
            return b;
        }



        private PHPVariable UnserializeArray()
        {
            this.readChar('a');
            this.readChar(':');
            int length = this.readInt();
            this.readChar(':');
            this.readChar('{');
            List<PHPArray.Item> list = new List<PHPArray.Item>();
            for (int i = 0; i < length; i++)
            {
                PHPVariable key = this.UnserializeVariable();

                PHPVariable value = this.UnserializeVariable();
                list.Add(new PHPArray.Item(key, value));

            }
            this.readChar('}');
            return new PHPArray(list.ToArray());
        }

        private PHPVariable UnserializeObject()
        {
            this.readChar('O');
            this.readChar(':');
            int len = this.readInt();
            this.readChar(':');
            this.readChar('"');
            string name = this.readString(len);
            this.readChar('"');
            this.readChar(':');
            int fieldsnum = this.readInt();
            this.readChar(':');
            this.readChar('{');
            List<PHPObject.Field> fields = new List<PHPObject.Field>();
            for (int i = 0; i < fieldsnum; i++)
            {
                PHPString fieldname = this.UnserializeString();
                PHPVariable fielvalue = this.UnserializeVariable();
                
                if (fieldname.Value.IndexOf('\0') == 0)
                {
                    string[] n =     fieldname.Value.Substring(1).Split('\0');
                    if (n[0] == "*")
                    {
                        
                        fields.Add(new PHPObject.ProtectedField(n[1], fielvalue));
                    }
                    else {
                        fields.Add(new PHPObject.PrivateField(n[1], fielvalue, n[0]));
                    }
                }
                else
                {
                    fields.Add(new PHPObject.PublicField(fieldname.Value, fielvalue));
                }

            }
            this.readChar('}');
            return new PHPObject(name, fields.ToArray());
        }

        private PHPVariable UnserializeFloat()
        {
            this.readChar('d');
            this.readChar(':');
            double val = this.readFloat();
            this.readChar(';');
            return new PHPFloat(val);
        }



        private PHPVariable UnserializeInt()
        {
            this.readChar('i');
            this.readChar(':');
            int val = this.readInt();
            this.readChar(';');
            return new PHPInt(val);
        }

        private PHPString UnserializeString()
        {
            this.readChar('s');
            this.readChar(':');
            int length = this.readInt();
            this.readChar(':');
            this.readChar('"');
            string str = this.readString(length);
            this.readChar('"');
            this.readChar(';');
            return new PHPString(str);

        }
        private PHPNull UnserializeNull()
        {
            this.readChar('N');
            this.readChar(';');
            return PHPNull.Null;
        }


        public static PHPVariable Unserialize(string str)
        {

            PHPUnserializer u = new PHPUnserializer(str);
            return u.UnserializeVariable();

        }
    }
}
namespace thesebas.php
{

    public class PHPVariable
    {
    }

    public class PHPNull : PHPVariable
    {
        private PHPNull() { }
        private static PHPNull __null = new PHPNull();
        public static PHPNull Null { get { return PHPNull.__null; } }
        public override string ToString()
        {
            return "PHPNull";
        }
    }

    public class PHPBoolean : PHPVariable
    {
        protected bool value;
        protected static PHPBoolean __true = new PHPBoolean(true);
        protected static PHPBoolean __false = new PHPBoolean(false);

        public static PHPBoolean True { get { return PHPBoolean.__true; } }
        public static PHPBoolean False { get { return PHPBoolean.__false; } }

        private PHPBoolean(bool v)
        {
            this.value = v;
        }
        public override string ToString()
        {
            return String.Format("PHPBoolean[{0}]", (this.value ? "true" : "false"));
        }

    }
    public class PHPArray : PHPVariable
    {
        public class Item
        {
            public PHPVariable Key { get { return this.key; } }
            public PHPVariable Value { get { return this.value; } }


            PHPVariable key;
            PHPVariable value;
            public Item(PHPVariable key, PHPVariable value)
            {
                this.key = key;
                this.value = value;
            }
        }
        public PHPArray.Item[] items;
        public PHPArray(PHPArray.Item[] items)
        {
            this.items = items;
        }
        public PHPArray(IEnumerable<PHPArray.Item> collection)
        {
            List<PHPArray.Item> l = new List<Item>(collection);
            this.items = l.ToArray();
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("PHPArray({0})\n", this.items.Length);

            foreach (PHPArray.Item item in this.items)
            {
                sb.AppendFormat("\t{0} => {1}\n", item.Key, item.Value);
            }
            return sb.ToString();
        }
    }
    public class PHPString : PHPVariable
    {
        public string Value { get { return this.value; } }
        string value;
        public PHPString(string s)
        {
            this.value = s;
        }
        public override string ToString()
        {
            return String.Format("PHPString[{0}]", this.value);
        }

    }
    public class PHPInt : PHPVariable
    {
        int value;
        public PHPInt(int val)
        {
            this.value = val;
        }

        public override string ToString()
        {
            return String.Format("PHPInt[{0}]", this.value);
        }
    }
    public class PHPFloat : PHPVariable
    {

        private static PHPFloatFormat format = new PHPFloatFormat();
        public static PHPFloatFormat Format
        {
            get { return PHPFloat.format; }
        }
        public class PHPFloatFormat : IFormatProvider, ICustomFormatter
        {

            #region IFormatProvider Members

            public object GetFormat(Type formatType)
            {
                if (formatType == typeof(System.Globalization.NumberFormatInfo))
                {
                    //Console.WriteLine("if");
                    System.Globalization.NumberFormatInfo i = new System.Globalization.NumberFormatInfo();
                    i.NumberDecimalSeparator = ".";
                    return i;
                }
                //Console.WriteLine("else");
                return null;
            }

            #endregion

            #region ICustomFormatter Members

            public string Format(string format, object arg, IFormatProvider formatProvider)
            {
                Console.WriteLine("cosie: [{0}][{1}][{2}]", format, arg, formatProvider);
                return "";
            }

            #endregion
        }
        double value;
        public PHPFloat(double val)
        {
            this.value = val;
        }
        public override string ToString()
        {
            return String.Format(PHPFloat.Format, "PHPFloat[{0}]", this.value);
        }
    }
    public class PHPObject : PHPVariable
    {
        string name;
        Field[] fields;
        public class Field
        {
            protected string name;
            protected PHPVariable value;
            public Field(string name, PHPVariable value)
            {
                this.name = name;
                this.value = value;
            }
        }
        public class PublicField : PHPObject.Field
        {
            public PublicField(string name, PHPVariable value) : base(name, value) { }
            public override string ToString()
            {
                return String.Format("  public\t{0}\t= {1}", this.name, this.value);
            }
        }
        public class ProtectedField : PHPObject.Field
        {
            public ProtectedField(string name, PHPVariable value) : base(name, value) { }
            public override string ToString()
            {
                return String.Format("  protected\t{0}\t= {1}", this.name, this.value);
            }

        }
        public class PrivateField : PHPObject.Field
        {
            public PrivateField(string name, PHPVariable value, string classfield)
                : base(name, value)
            {
                this.classfield = classfield;
            }
            private string classfield;
            public override string ToString()
            {
                return String.Format("  private\t{2}.{0}\t= {1}", this.name, this.value, this.classfield);
            }

        }
        public PHPObject(string classname, PHPObject.Field[] fields)
        {
            this.fields = fields;
            this.name = classname;
        }

        public PHPObject(IEnumerable<PHPObject.Field> fields)
        {
            List<PHPObject.Field> l = new List<Field>(fields);
            this.fields = l.ToArray();
        }
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("PHPObject[{0}]\n", this.name);
            foreach (PHPObject.Field field in this.fields)
            {
                sb.AppendLine(field.ToString());
            }
            return sb.ToString();
        }

    }
}
